﻿using System;

namespace Aeon.Emulator.Memory
{
    /// <summary>
    /// Provides read/write access to emulated physical memory without paging.
    /// </summary>
    internal sealed class LinearMemoryAccessor : MemoryAccessor
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the LinearMemoryAccessor class.
        /// </summary>
        /// <param name="rawView">Emulated physical memory.</param>
        public LinearMemoryAccessor(IntPtr rawView)
            : base(rawView)
        {
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Reads a byte from emulated memory.
        /// </summary>
        /// <param name="address">Physical address of byte to read.</param>
        /// <returns>Byte at the specified address.</returns>
        public override byte GetByte(uint address)
        {
            if(address >= PhysicalMemory.VramAddress && address < PhysicalMemory.VramUpperBound)
                return video.GetVramByte(address - PhysicalMemory.VramAddress);

            unsafe
            {
                return RawView[address];
            }
        }
        /// <summary>
        /// Writes a byte to emulated memory.
        /// </summary>
        /// <param name="address">Physical address of byte to write.</param>
        /// <param name="value">Value to write to the specified address.</param>
        public override void SetByte(uint address, byte value)
        {
            if(address >= PhysicalMemory.VramAddress && address < PhysicalMemory.VramUpperBound)
            {
                video.SetVramByte(address - PhysicalMemory.VramAddress, value);
                return;
            }
            
            unsafe
            {
                RawView[address] = value;
            }
        }

        /// <summary>
        /// Reads an unsigned 16-bit integer from emulated memory.
        /// </summary>
        /// <param name="address">Physical address of unsigned 16-bit integer to read.</param>
        /// <returns>Unsigned 16-bit integer at the specified address.</returns>
        public override ushort GetUInt16(uint address)
        {
            if(address >= PhysicalMemory.VramAddress && address < PhysicalMemory.VramUpperBound)
                return video.GetVramWord(address - PhysicalMemory.VramAddress);

            unsafe
            {
                return *(ushort*)(RawView + address);
            }
        }
        /// <summary>
        /// Writes an unsigned 16-bit integer to emulated memory.
        /// </summary>
        /// <param name="address">Physical address of unsigned 16-bit integer to write.</param>
        /// <param name="value">Value to write to the specified address.</param>
        public override void SetUInt16(uint address, ushort value)
        {
            if(address >= PhysicalMemory.VramAddress && address < PhysicalMemory.VramUpperBound)
            {
                video.SetVramWord(address - PhysicalMemory.VramAddress, value);
                return;
            }

            unsafe
            {
                *(ushort*)(RawView + address) = value;
            }
        }

        /// <summary>
        /// Reads an unsigned 32-bit integer from emulated memory.
        /// </summary>
        /// <param name="address">Physical address of unsigned 32-bit integer to read.</param>
        /// <returns>Unsigned 32-bit integer at the specified address.</returns>
        public override uint GetUInt32(uint address)
        {
            if(address >= PhysicalMemory.VramAddress && address < PhysicalMemory.VramUpperBound)
                return video.GetVramDWord(address - PhysicalMemory.VramAddress);

            unsafe
            {
                return *(uint*)(RawView + address);
            }
        }
        /// <summary>
        /// Writes an unsigned 32-bit integer to emulated memory.
        /// </summary>
        /// <param name="address">Physical address of unsigned 32-bit integer to write.</param>
        /// <param name="value">Value to write to the specified address.</param>
        public override void SetUInt32(uint address, uint value)
        {
            if(address >= PhysicalMemory.VramAddress && address < PhysicalMemory.VramUpperBound)
            {
                video.SetVramDWord(address - PhysicalMemory.VramAddress, value);
                return;
            }

            unsafe
            {
                *(uint*)(RawView + address) = value;
            }
        }

        /// <summary>
        /// Reads an unsigned 64-bit integer from emulated memory.
        /// </summary>
        /// <param name="address">Physical address of unsigned 64-bit integer to read.</param>
        /// <returns>Unsigned 64-bit integer at the specified address.</returns>
        public override ulong GetUInt64(uint address)
        {
            unsafe
            {
                return *(ulong*)(RawView + address);
            }
        }
        /// <summary>
        /// Writes an unsigned 64-bit integer to emulated memory.
        /// </summary>
        /// <param name="address">Physical address of unsigned 64-bit integer to write.</param>
        /// <param name="value">Value to write to the specified address.</param>
        public override void SetUInt64(uint address, ulong value)
        {
            unsafe
            {
                *(ulong*)(RawView + address) = value;
            }
        }

        /// <summary>
        /// Returns a System.Single value read from an address in emulated memory.
        /// </summary>
        /// <param name="address">Address of value to read.</param>
        /// <returns>32-bit System.Single value read from the specified address.</returns>
        public override float GetReal32(uint address)
        {
            unsafe
            {
                return *(float*)(RawView + address);
            }
        }
        /// <summary>
        /// Writes a System.Single value to an address in emulated memory.
        /// </summary>
        /// <param name="address">Address where value will be written.</param>
        /// <param name="value">32-bit System.Single value to write at the specified address.</param>
        public override void SetReal32(uint address, float value)
        {
            unsafe
            {
                *(float*)(RawView + address) = value;
            }
        }

        /// <summary>
        /// Returns a System.Double value read from an address in emulated memory.
        /// </summary>
        /// <param name="address">Address of value to read.</param>
        /// <returns>64-bit System.Double value read from the specified address.</returns>
        public override double GetReal64(uint address)
        {
            unsafe
            {
                return *(double*)(RawView + address);
            }
        }
        /// <summary>
        /// Writes a System.Double value to an address in emulated memory.
        /// </summary>
        /// <param name="address">Address where value will be written.</param>
        /// <param name="value">64-bit System.Double value to write at the specified address.</param>
        public override void SetReal64(uint address, double value)
        {
            unsafe
            {
                *(double*)(RawView + address) = value;
            }
        }

        /// <summary>
        /// Returns a Real10 value read from an address in emulated memory.
        /// </summary>
        /// <param name="address">Address of value to read.</param>
        /// <returns>80-bit Real10 value read from the specified address.</returns>
        public override Real10 GetReal80(uint address)
        {
            unsafe
            {
                return *(Real10*)(RawView + address);
            }
        }
        /// <summary>
        /// Writes a Real10 value to an address in emulated memory.
        /// </summary>
        /// <param name="address">Address where value will be written.</param>
        /// <param name="value">80-bit Real10 value to write at the specified address.</param>
        public override void SetReal80(uint address, Real10 value)
        {
            unsafe
            {
                *(Real10*)(RawView + address) = value;
            }
        }

        /// <summary>
        /// Reads 16 bytes from emulated memory into a buffer.
        /// </summary>
        /// <param name="address">Address where bytes will be read from.</param>
        /// <param name="buffer">Buffer into which bytes will be written.</param>
        public override unsafe void FetchInstruction(uint address, byte* buffer)
        {
            *(ulong*)buffer = *(ulong*)(RawView + address);
            ((ulong*)buffer)[1] = ((ulong*)(RawView + address))[1];
        }

        /// <summary>
        /// Returns a pointer to a block of memory, making sure it is paged in.
        /// </summary>
        /// <param name="address">Logical address of block.</param>
        /// <param name="size">Number of bytes in block of memory.</param>
        /// <returns>Pointer to block of memory.</returns>
        public override unsafe void* GetSafePointer(uint address, uint size)
        {
            return RawView + address;
        }
        #endregion
    }
}
